package net.java.otr4j.io;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPublicKey;

import javax.crypto.interfaces.DHPublicKey;

import net.java.otr4j.io.messages.SignatureM;
import net.java.otr4j.io.messages.MysteriousT;
import net.java.otr4j.io.messages.SignatureX;

import org.bouncycastle.util.BigIntegers;

public class OtrOutputStream extends FilterOutputStream implements
		SerializationConstants {

	public OtrOutputStream(OutputStream out) {
		super(out);
	}

	private void writeNumber(int value, int length) throws IOException {
		byte[] b = new byte[length];
		for (int i = 0; i < length; i++) {
			int offset = (b.length - 1 - i) * 8;
			b[i] = (byte) ((value >>> offset) & 0xFF);
		}
		write(b);
	}

	public void writeBigInt(BigInteger bi) throws IOException {
		byte[] b = BigIntegers.asUnsignedByteArray(bi);
		writeData(b);
	}

	public void writeByte(int b) throws IOException {
		writeNumber(b, TYPE_LEN_BYTE);
	}

	public void writeData(byte[] b) throws IOException {
		int len = (b == null || b.length < 0) ? 0 : b.length;
		writeNumber(len, DATA_LEN);
		if (len > 0)
			write(b);
	}

	public void writeInt(int i) throws IOException {
		writeNumber(i, TYPE_LEN_INT);

	}

	public void writeShort(int s) throws IOException {
		writeNumber(s, TYPE_LEN_SHORT);

	}

	public void writeMac(byte[] mac) throws IOException {
		if (mac == null || mac.length != TYPE_LEN_MAC)
			throw new IllegalArgumentException();

		write(mac);
	}

	public void writeCtr(byte[] ctr) throws IOException {
		if (ctr == null || ctr.length < 1)
			return;

		int i = 0;
		while (i < TYPE_LEN_CTR && i < ctr.length) {
			write(ctr[i]);
			i++;
		}
	}

	public void writeDHPublicKey(DHPublicKey dhPublicKey) throws IOException {
		byte[] b = BigIntegers.asUnsignedByteArray(dhPublicKey.getY());
		writeData(b);
	}

	public void writePublicKey(PublicKey pubKey) throws IOException {
		if (!(pubKey instanceof DSAPublicKey))
			throw new UnsupportedOperationException(
					"Key types other than DSA are not supported at the moment.");

		DSAPublicKey dsaKey = (DSAPublicKey) pubKey;

		writeShort(0);

		DSAParams dsaParams = dsaKey.getParams();
		writeBigInt(dsaParams.getP());
		writeBigInt(dsaParams.getQ());
		writeBigInt(dsaParams.getG());
		writeBigInt(dsaKey.getY());

	}

	public void writeTlvData(byte[] b) throws IOException {
		int len = (b == null || b.length < 0) ? 0 : b.length;
		writeNumber(len, TLV_LEN);
		if (len > 0)
			write(b);
	}

	public void writeSignature(byte[] signature, PublicKey pubKey)
			throws IOException {
		if (!pubKey.getAlgorithm().equals("DSA"))
			throw new UnsupportedOperationException();
		out.write(signature);
	}

	public void writeMysteriousX(SignatureX x) throws IOException {
		writePublicKey(x.longTermPublicKey);
		writeInt(x.dhKeyID);
		writeSignature(x.signature, x.longTermPublicKey);
	}

	public void writeMysteriousX(SignatureM m) throws IOException {
		writeBigInt(m.localPubKey.getY());
		writeBigInt(m.remotePubKey.getY());
		writePublicKey(m.localLongTermPubKey);
		writeInt(m.keyPairID);
	}

	public void writeMysteriousT(MysteriousT t) throws IOException {
		writeShort(t.protocolVersion);
		writeByte(t.messageType);
		if (t.protocolVersion == 3) {
			writeInt(t.senderInstanceTag);
			writeInt(t.receiverInstanceTag);
		}
		writeByte(t.flags);

		writeInt(t.senderKeyID);
		writeInt(t.recipientKeyID);
		writeDHPublicKey(t.nextDH);
		writeCtr(t.ctr);
		writeData(t.encryptedMessage);

	}
}
